﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Xml.Linq;
using System.IO;
using Microsoft.SharePoint.Publishing.Fields;
using AgileShare.Framework.Core;
using AgileShare.Framework.Library.Common;

namespace AgileShare.Framework.Library.Helpers
{
	public class SPFieldHelper
	{
		/// <summary>
		/// Changes the display name of the field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="fieldDisplayName">Display name of the field.</param>
		/// <param name="list">The list.</param>
		/// <param name="web">The web.</param>
		public static void ChangeFieldDisplayName(Guid fieldId, string fieldDisplayName, SPList list, SPWeb web)
		{
			SPField field = list.Fields[fieldId];
			field.Title = fieldDisplayName;
			field.Update();
		}

		/// <summary>
		/// Changes the display name of the field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="fieldDisplayName">Display name of the field.</param>
		/// <param name="listUrl">The list URL.</param>
		/// <param name="web">The web.</param>
		public static void ChangeFieldDisplayName(Guid fieldId, string fieldDisplayName, string listUrl, SPWeb web)
		{
			SPList list = web.GetList(listUrl);
			ChangeFieldDisplayName(fieldId, fieldDisplayName, list, web);
		}

		/// <summary>
		/// Changes the display name of the field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="fieldDisplayName">Display name of the field.</param>
		/// <param name="list">The list.</param>
		/// <param name="site">The site.</param>
		public static void ChangeFieldDisplayName(Guid fieldId, string fieldDisplayName, SPList list, SPSite site)
		{
			SPField field = site.RootWeb.Fields[fieldId];
			field.Title = fieldDisplayName;
			field.Update();
		}

		/// <summary>
		/// Ensures the field.
		/// </summary>
		/// <param name="fieldXml">The field XML.</param>
		/// <param name="fieldCollection">The field collection.</param>
		/// <param name="updateIfExists">if set to <c>true</c> [update if exists].</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		/// <returns></returns>
		public static SPField EnsureField(XElement fieldXml, SPFieldCollection fieldCollection, bool updateIfExists = false, bool pushChangesToLists = false)
		{			
			string displayName = fieldXml.Attribute("DisplayName").Value;
			string internalName = fieldXml.Attribute("Name").Value;
			// The AddFieldAsXml wrongly uses the DisplayName as internal name. Switching it, and then setting it back in the added SPField.
			fieldXml.Attribute("DisplayName").SetValue(internalName);

			Guid fieldId = new Guid(fieldXml.Attribute("ID").Value);

			TraceProvider.Log(TraceProviderCategories.Default, (uint)TraceProviderEventIds.SPFieldHelper, TraceProviderSeverity.Information,
				"Ensuring Field '{0}'; ID: '{1}';", internalName, fieldId);

			SPField field = null;
			try
			{

				if (!fieldCollection.Contains(fieldId))
				{
					fieldCollection.AddFieldAsXml(fieldXml.ToString());
					field = fieldCollection[fieldId];
					field.Title = displayName;
					field.Update();
				}
				else if (updateIfExists)
				{
					field = fieldCollection[fieldId];
					field.SchemaXml = fieldXml.ToString();
					field.Title = displayName;
					field.Update(pushChangesToLists);
				}
				
				TraceProvider.Log(TraceProviderCategories.Default, (uint)TraceProviderEventIds.SPFieldHelper, TraceProviderSeverity.Monitorable,
					"Successfully ensured Field '{0}'; ID: '{1}';", internalName, fieldId);
			}
			catch (Exception ex)
			{
				TraceProvider.Log(TraceProviderCategories.Default, (uint)TraceProviderEventIds.SPFieldHelper, TraceProviderSeverity.CriticalError, ex,
					"Failed to ensure Field '{0}'; ID: '{1}'; ", internalName, fieldId);
			}
			return field;
		}

		/// <summary>
		/// Ensures the fields from XML document.
		/// </summary>
		/// <param name="xmlDocumentUri">The XML document URI.</param>
		/// <param name="fieldCollection">The field collection.</param>
		/// <param name="updateIfExists">if set to <c>true</c> [update if exists].</param>
		/// <param name="pushChangesToList">if set to <c>true</c> [push changes to list].</param>
		public static void EnsureFieldsFromXmlDocument(string xmlDocumentUri, SPFieldCollection fieldCollection, bool updateIfExists = false, bool pushChangesToList = false)
		{
			XDocument document = XDocument.Load(xmlDocumentUri);
			var fields = document.ElementsAfterSelf().ToArray();
			EnsureFields(fieldCollection, updateIfExists, pushChangesToList, fields);
		}

		/// <summary>
		/// Ensures the fields.
		/// </summary>
		/// <param name="fieldCollection">The field collection.</param>
		/// <param name="updateIfExists">if set to <c>true</c> [update if exists].</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		/// <param name="fields">The fields.</param>
		public static void EnsureFields(SPFieldCollection fieldCollection, bool updateIfExists, bool pushChangesToLists, params XElement[] fields)
		{
			foreach (XElement field in fields)
			{
				EnsureField(field, fieldCollection, updateIfExists, pushChangesToLists);
			}
		}

		/// <summary>
		/// Adds the site column to list.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="list">The list.</param>
		public static void AddSiteColumnToList(Guid fieldId, SPList list)
		{
			SPWeb web = list.ParentWeb.Site.RootWeb;
			if (web.Fields.Contains(fieldId) && !list.Fields.Contains(fieldId))
			{
				SPField field = web.Fields[fieldId];
				list.Fields.Add(field);
				list.Update();
			}
		}

		/// <summary>
		/// Adds the site column to list.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="list">The list.</param>
		public static void AddSiteColumnsToList(SPList list, params Guid[] fieldIds)
		{
			foreach (Guid fieldId in fieldIds)
			{
				AddSiteColumnToList(fieldId, list);
			}
		}

		/// <summary>
		/// Deletes the field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="web">The web.</param>
		public static void DeleteField(Guid fieldId, bool deleteIfHasUsages, SPSite site)
		{
			SPWeb web = site.RootWeb;
			if (web.Fields.Contains(fieldId))
			{
				SPField field = web.Fields[fieldId];
				ICollection<SPFieldTemplateUsage> fieldTemplateUsages = field.ListsFieldUsedIn();
				if (fieldTemplateUsages.Count == 0 || deleteIfHasUsages)
				{
					try
					{
						foreach (SPFieldTemplateUsage fieldTemplateUsage in fieldTemplateUsages)
						{
							using (SPWeb usageWeb = web.Site.OpenWeb(fieldTemplateUsage.WebID))
							{
								SPList list = usageWeb.Lists[fieldTemplateUsage.ListID];

								if (list.ContentTypesEnabled)
								{
									SPContentTypeCollection listContentTypes = list.ContentTypes;
									foreach (SPContentType contentType in listContentTypes)
									{
										if (contentType.Fields.ContainsField(field.InternalName))
										{
											HardDeleteFieldLink(field, contentType, null);
										}
									}
								}

								if (list.Fields.ContainsField(field.InternalName))
								{
									list.Fields.Delete(field.InternalName);
								}
							}
						}

						foreach (SPContentType contentType in web.ContentTypes)
						{
							if (contentType.Fields.ContainsField(field.InternalName))
							{
								HardDeleteFieldLink(field, contentType, true);
							}
						}
						field.Delete();
					}
					catch (SPException ex)
					{
						string errorMessage = string.Format("Could not delete Field with name {0} and id {1}'.", field.Title, field.Id);
						TraceProvider.Log(errorMessage, TraceProviderCategories.Default, (uint)TraceProviderEventIds.SPContentTypeHelper, TraceProviderSeverity.Warning, ex);
					}
				}
			}

		}

		/// <summary>
		/// Hards the delete field link.
		/// </summary>
		/// <param name="field">The field.</param>
		/// <param name="contentType">Type of the content.</param>
		/// <param name="updateChildren">The update children.</param>
		public static void HardDeleteFieldLink(SPField field, SPContentType contentType, bool? updateChildren)
		{
			bool wasSealed = contentType.Sealed;
			bool wasReadOnly = contentType.ReadOnly;

			contentType.Sealed = false;
			contentType.ReadOnly = false;

			contentType.FieldLinks.Delete(field.InternalName);
			if (updateChildren.HasValue)
			{
				contentType.Update(updateChildren.Value);
			}
			else
			{
				contentType.Update();
			}

			contentType.Sealed = wasSealed;
			contentType.ReadOnly = wasReadOnly;
		}

		/// <summary>
		/// Deletes the fields.
		/// </summary>
		/// <param name="web">The web.</param>
		/// <param name="fieldIds">The field ids.</param>
		public static void DeleteFields(SPSite site, bool deleteIfHasUsages, params Guid[] fieldIds)
		{
			foreach (Guid fieldId in fieldIds)
			{
				DeleteField(fieldId, deleteIfHasUsages, site);
			}
		}

		/// <summary>
		/// Removes the field.
		/// </summary>
		/// <param name="fieldId">The field id.</param>
		/// <param name="list">The list.</param>
		public static void RemoveField(Guid fieldId, SPList list)
		{
			SPFieldCollection fieldCollection = list.Fields;
			if (fieldCollection.Contains(fieldId))
			{
				SPField field = fieldCollection[fieldId];
				fieldCollection.Delete(field.InternalName);
			}
		}

		/// <summary>
		/// Removes the fields.
		/// </summary>
		/// <param name="list">The list.</param>
		/// <param name="fieldIds">The field ids.</param>
		public static void RemoveFields(SPList list, params Guid[] fieldIds)
		{
			foreach (Guid fieldId in fieldIds)
			{
				RemoveField(fieldId, list);
			}
		}

		/// <summary>
		/// Updates the lookup references.
		/// </summary>
		/// <param name="lookupField">The lookup field.</param>
		/// <param name="webId">The web id.</param>
		/// <param name="listId">The list id.</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		public static void UpdateLookupReferences(SPFieldLookup lookupField, Guid webId, Guid listId, bool pushChangesToLists)
		{
			if (string.IsNullOrEmpty(lookupField.LookupList))
			{
				lookupField.LookupWebId = webId;
				lookupField.LookupList = listId.ToString();
			}
			else
			{
				lookupField.SchemaXml =
					lookupField.SchemaXml
						.ReplaceXmlAttributeValue("List", listId.ToString())
						.ReplaceXmlAttributeValue("WebId", webId.ToString());
			}

			lookupField.Update(pushChangesToLists);
		}

		/// <summary>
		/// Updates the lookup references.
		/// </summary>
		/// <param name="lookupFieldId">The lookup field id.</param>
		/// <param name="list">The list.</param>
		/// <param name="pushChangesToLists">if set to <c>true</c> [push changes to lists].</param>
		public static void UpdateLookupReferences(Guid lookupFieldId, SPList list, bool pushChangesToLists)
		{
			SPWeb web = list.ParentWeb;

			Guid webId = web.ID;
			Guid listId = list.ID;

			SPFieldLookup lookupField = (SPFieldLookup)web.Fields[lookupFieldId];
			if (string.IsNullOrEmpty(lookupField.LookupList))
			{
				lookupField.LookupWebId = webId;
				lookupField.LookupList = listId.ToString();
			}
			else
			{
				lookupField.SchemaXml =
					lookupField.SchemaXml
						.ReplaceXmlAttributeValue("List", listId.ToString())
						.ReplaceXmlAttributeValue("WebId", webId.ToString());
			}

			lookupField.Update(pushChangesToLists);
		}

	}
}
/* [UpdateField - Attempt]
///// <summary>
///// Updates the field.
///// </summary>
///// <param name="def">The def.</param>
///// <param name="fieldCollection">The field collection.</param>
//public static void UpdateField(FieldDefinition def, SPFieldCollection fieldCollection)
//{
//    if (fieldCollection.Contains(def.ID))
//    {
//        SPField field = fieldCollection[def.ID];
//        field.SchemaXml = def.DefinitionXml.ToString();
//        return;

//        field.Title = def.DisplayName;
//        field.Description = def.Description;
//        field.Group = def.Group;
//        field.Required = def.Required;
//        field.Update();

//        switch (def.Type)
//        {
//            case FieldType.Text:
//                var fieldText = field as SPFieldText;
//                if (fieldText != null)
//                {
//                    var textDef = (FieldDefinitionText)def;

//                    fieldText.MaxLength = textDef.MaxLength ?? 255;
//                    fieldText.Update();
//                }
//                break;

//            case FieldType.Number:
//                var fieldNumber = field as SPFieldNumber;
//                if (fieldNumber != null)
//                {
//                    var numberDef = (FieldDefinitionNumber)def;

//                    fieldNumber.ShowAsPercentage = numberDef.Percentage;
//                    fieldNumber.DisplayFormat = numberDef.Decimals.HasValue ? (SPNumberFormatTypes)numberDef.Decimals.Value : SPNumberFormatTypes.Automatic;
//                    fieldNumber.MaximumValue = numberDef.Max ?? double.MaxValue;
//                    fieldNumber.MinimumValue = numberDef.Min ?? double.MinValue;
//                    fieldNumber.Update();
//                }
//                break;

//            case FieldType.Currency:
//                var fieldCurrency = field as SPFieldCurrency;
//                if (fieldCurrency != null)
//                {
//                    var currencyDef = (FieldDefinitionCurrency)def;

//                    fieldCurrency.DisplayFormat = currencyDef.Decimals.HasValue ? (SPNumberFormatTypes)currencyDef.Decimals.Value : SPNumberFormatTypes.Automatic;
//                    fieldCurrency.MaximumValue = currencyDef.Max ?? double.MaxValue;
//                    fieldCurrency.MinimumValue = currencyDef.Min ?? double.MinValue;
//                    fieldCurrency.Update();
//                }
//                break;

//            case FieldType.DateTime:
//                var fieldDateTime = field as SPFieldDateTime;
//                if (fieldDateTime != null)
//                {
//                    var dateTimeDef = (FieldDefinitionDateTime)def;

//                    fieldDateTime.DisplayFormat = (SPDateTimeFieldFormatType)dateTimeDef.Format.GetValueOrDefault(FieldFormatDateTime.DateTime);
//                    fieldDateTime.Update();
//                }
//                break;

//            case FieldType.Note:
//                var fieldNote = field as SPFieldMultiLineText;
//                if (fieldNote != null)
//                {
//                    var noteDef = (FieldDefinitionNote)def;

//                    fieldNote.NumberOfLines = noteDef.LinesToDisplay ?? 6;
//                    fieldNote.RichText = noteDef.RichTextMode.HasValue;
//                    if (noteDef.RichTextMode.HasValue)
//                    {
//                        fieldNote.RichTextMode = (SPRichTextMode)noteDef.RichTextMode.Value;
//                    }
//                    fieldNote.Update();
//                }
//                break;


//            case FieldType.Choice:
//            case FieldType.MultiChoice:
//                var fieldChoice = fieldCollection[field.Id] as SPFieldMultiChoice;
//                if (fieldChoice != null)
//                {
//                    var choiceDef = (FieldDefinitionChoice)def;
//                    fieldChoice.FillInChoice = choiceDef.FillInChoice;
//                    foreach (string choice in choiceDef.Choices)
//                    {
//                        if (!fieldChoice.Choices.Cast<string>().Any(x => x == choice))
//                        {
//                            fieldChoice.Choices.Add(choice);
//                        }
//                    }
//                    fieldChoice.Update();

//                    if (fieldChoice is SPFieldChoice && choiceDef.AllowMultipleValues)
//                    {
//                        ChangeFieldType<SPFieldMultiChoice>(fieldCollection[field.Id], FieldType.MultiChoice, fieldCollection);
//                    }
//                    else
//                    {
//                        var fieldSingleChoice = ChangeFieldType<SPFieldChoice>(fieldCollection[field.Id], FieldType.Choice, fieldCollection);
//                        fieldSingleChoice.EditFormat = (SPChoiceFormatType)choiceDef.Format;
//                        fieldSingleChoice.Update();
//                    }
//                    // fieldChoice.Update();
//                }
//                break;

//            case FieldType.URL:
//                var fieldUrl = field as SPFieldUrl;
//                if (fieldUrl != null)
//                {
//                    var urlDef = (FieldDefinitionUrl)def;

//                    fieldUrl.DisplayFormat = (SPUrlFieldFormatType)urlDef.Format;
//                    fieldUrl.Update();
//                }
//                break;

//            case FieldType.Lookup:
//            case FieldType.LookupMulti:
//                var fieldLookup = field as SPFieldLookup;
//                if (fieldLookup != null)
//                {
//                    var lookupDef = (FieldDefinitionLookup)def;

//                    UpdateLookupReferences(fieldLookup, fieldLookup.LookupWebId, lookupDef.LookupListId, true);
//                    fieldLookup.LookupField = lookupDef.ShowField;
//                    fieldLookup.RelatedField = lookupDef.FieldRef.HasValue ? lookupDef.FieldRef.Value.ToString() : null;
//                    fieldLookup.AllowMultipleValues = lookupDef.AllowMultipleValues;
//                    if (lookupDef.AllowMultipleValues)
//                    {
//                        fieldLookup.PrependId = lookupDef.PrependId;
//                    }
//                    fieldLookup.SchemaXml = fieldLookup.SchemaXml.ReplaceXmlAttributeValue("Type", lookupDef.Type.ToString());
//                    fieldLookup.Update();							
//                }
//                break;

//            case FieldType.Boolean:
//            case FieldType.Integer:
//            case FieldType.UrlSelectorField:
//            case FieldType.HTML:
//            case FieldType.Image:
//                // Nothing to update unless there is a type change.
//                break;
//        }
//    }
//}
*/